<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>
      
    线程编程指导 第四章 - Arisu
    
    </title>
    <link rel="shortcut icon" href="https://ushio.oss-cn-shanghai.aliyuncs.com/Arisu/Arisu_Icon.png" type="image/png" />

    
    
    <link href="atom.xml" rel="alternate" title="Arisu" type="application/atom+xml">
    <link rel="stylesheet" href="asset/css/style.min.css">
    <link rel="stylesheet" href="asset/css/doc.css">
    <script src="asset/app.js"></script>
</head>
  <body>
    <section class="hero">
      <div class="hero-head">
          <nav class="navbar" role="navigation" aria-label="main navigation">
              <div class="container">
              <div class="navbar-brand">
                
                <a target="_self" class="navbar-item " href="index.html">主页</a>
                
                <a target="_self" class="navbar-item " href="Apple.html">Apple</a>
                

                <a role="button" id="navbarSNSRssSwitchBtn" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarSNSRssButtons">
                  <span aria-hidden="true"></span>
                  <span aria-hidden="true"></span>
                  <span aria-hidden="true"></span>
                </a>
              </div>
            
              <div id="navbarSNSRssButtons" class="navbar-menu">
                <div class="navbar-start">
                  
                </div>
            
                <div class="navbar-end">
                  <div class="navbar-item">
                    <!--buttons start-->
                    <div class="buttons">
                      
                        
                        
                        
                        
                      
                      <a href="atom.xml" target="_blank" title="RSS">
                          <span class="icon is-large has-text-black-bis">
                              <svg class="svg-inline--fa fa-rss fa-w-14 fa-lg" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="rss" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg=""><path fill="currentColor" d="M128.081 415.959c0 35.369-28.672 64.041-64.041 64.041S0 451.328 0 415.959s28.672-64.041 64.041-64.041 64.04 28.673 64.04 64.041zm175.66 47.25c-8.354-154.6-132.185-278.587-286.95-286.95C7.656 175.765 0 183.105 0 192.253v48.069c0 8.415 6.49 15.472 14.887 16.018 111.832 7.284 201.473 96.702 208.772 208.772.547 8.397 7.604 14.887 16.018 14.887h48.069c9.149.001 16.489-7.655 15.995-16.79zm144.249.288C439.596 229.677 251.465 40.445 16.503 32.01 7.473 31.686 0 38.981 0 48.016v48.068c0 8.625 6.835 15.645 15.453 15.999 191.179 7.839 344.627 161.316 352.465 352.465.353 8.618 7.373 15.453 15.999 15.453h48.068c9.034-.001 16.329-7.474 16.005-16.504z"></path></svg><!-- <i class="fas fa-rss fa-lg"></i> -->
                          </span>
                      </a>
                    </div>
                    <!--buttons end-->

                  </div>
                </div>
                </div>
              </div>
            </nav>
      </div>

 <div class="hero-body ct-body"></div>
      
    </section>
    <section class="ct-body">
      <div class="container">
          <div class="columns is-variable bd-klmn-columns is-4 is-centered">
              <div class="column is-four-fifths">
                  <div class="post-body single-content">
                    
                    <h1 class="title">
                            线程编程指导 第四章   
                      </h1>
                     
                    
                      <div class="media">
                            
                            <figure class="media-left">
                              <p class="image is-48x48">
                                
                                  <img class="is-rounded" src="https://ushio.oss-cn-shanghai.aliyuncs.com/Arisu/Arisu_Icon.png">
                                
                              </p>
                            </figure>
                            
                            <div class="media-content">
                              <div class="content">
                                <p>
                                 <span class="date">2020/02/25</span>
                                  <span class="tran-posted-in">posted in</span>&nbsp; 
                                  
                                      <span class="posted-in"><a href='Apple%20Developer%20Documentation.html'>Apple Developer Documentation</a></span>
                                  
                                      <span class="posted-in"><a href='%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B%E6%8C%87%E5%AF%BC.html'>线程编程指导</a></span>
                                         
                                  

                                   
                                      
                                  <br />
                                  <span class="tran-tags">Tags:</span>&nbsp;
                                  
                                    <a class="tag is-link is-light" href='tag_%E6%8A%80%E6%9C%AF%E6%96%87%E6%A1%A3.html'>#技术文档</a>
                                  
                                    <a class="tag is-link is-light" href='tag_Arisu.html'>#Arisu</a>
                                     

                                </p>
                              </div>
                            </div>
                         
                    </div>
                </div>
                  <article class="markdown-body single-content">
                    <h1><a id="%E5%90%8C%E6%AD%A5" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>同步</h1>
<p>应用程序中存在多个线程会导致从多个执行线程安全访问资源的潜在问题。修改相同资源的两个线程可能会以非预期的方式相互干扰。例如，一个线程可能会覆盖另一个线程的更改，或者将应用程序置于未知且可能无效的状态。如果幸运的话，损坏的资源可能会导致明显的性能问题或崩溃，相对容易追踪和修复。但是，如果您运气不好，则损坏可能会导致细微错误，这些错误直到很久之后才会显现，或者错误可能需要对您的基础编码假设进行重大改进。</p>
<p>在线程安全方面，良好的设计是您拥有的最佳保护。避免共享资源并最小化线程之间的交互使得这些线程不太可能相互干扰。但是，并非总能实现完全无干扰的设计。如果您的线程必须进行交互，则需要使用同步工具来确保它们在交互时安全地进行交互。</p>
<p>OS X 和 iOS 提供了许多同步工具供您使用，从提供互斥访问的工具到在应用程序中正确排序事件的工具。以下部分介绍了这些工具以及如何在代码中使用它们来影响对程序资源的安全访问。</p>
<h2><a id="%E5%90%8C%E6%AD%A5%E5%B7%A5%E5%85%B7" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>同步工具</h2>
<p>为防止不同的线程意外更改数据，您可以将应用程序设计为不存在同步问题，也可以使用同步工具。虽然完全避免同步问题是可取的，但并不总是可行的。以下部分介绍了可供您使用的同步工具的基本类别。</p>
<h3><a id="%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>原子操作</h3>
<p><u>原子操作是一种简单的同步形式，适用于简单的数据类型。原子操作的优点是它们不会阻止竞争线程。对于简单的操作，例如递增计数器变量，这可以带来比锁定更好的性能。</u></p>
<p>OS X 和 iOS 包含许多操作，可对 32 位和 64 位值执行基本的数学和逻辑运算。这些操作包括比较和交换，测试和设置以及测试和清除操作的原子版本。有关受支持的原子操作的列表，请参阅 /usr/include/libkern/OSAtomic.h 头文件或参见<a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/atomic.3.html#//apple_ref/doc/man/3/atomic">原子手册页</a>。</p>
<h3><a id="%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C%E5%92%8C%E6%98%93%E5%A4%B1%E6%80%A7%E5%8F%98%E9%87%8F%EF%BC%88volatile%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>内存屏障和易失性变量（Volatile）</h3>
<p>为了获得最佳性能，编译器通常会重新排序汇编级指令，以使处理器的指令流尽可能完整。作为此优化的一部分，编译器可能会重新排序访问主内存的指令，因为它认为这样做不会生成不正确的数据。遗憾的是，编译器并不总是能够检测所有与内存相关的操作。如果看似单独的变量实际上相互影响，则编译器优化可能会以错误的顺序更新这些变量，从而产生可能不正确的结果。</p>
<p>内存屏障是一种非阻塞同步工具，用于确保内存操作以正确的顺序发生。存储器屏障的作用类似于栅栏，迫使处理器在允许执行位于屏障之后的加载和存储操作之前完成位于屏障前面的任何加载和存储操作。内存屏障通常用于确保一个线程（但对另一个线程可见）的内存操作始终以预期的顺序发生。在这种情况下缺乏内存屏障可能允许其他线程看到看似不可能的结果。（例如，请参阅 Wikipedia 条目以了解<a href="https://en.wikipedia.org/wiki/Memory_barrier">内存屏障</a>。）要使用内存屏障，只需在代码中的适当位置调用 <code>OSMemoryBarrier</code> 函数即可。</p>
<p>易失性变量将另一种类型的内存约束应用于各个变量。编译器通常通过将变量的值加载到寄存器中来优化代码。对于局部变量，这通常不是问题。但是，如果该变量在另一个线程中可见，则此类优化可能会阻止另一个线程注意到对它的任何更改。将 <code>volatile</code> 关键字应用于变量会强制编译器在每次使用时从内存加载该变量。如果变量的值可以随时由编译器可能无法检测到的外部源更改，则可以将变量声明为 <code>volatile</code>。</p>
<p>因为内存屏障和 <code>volatile</code> 变量都会减少编译器可以执行的优化次数，所以应该谨慎使用它们，并且只在需要确保正确性的地方使用它们。有关使用内存屏障的信息，请参见 <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/OSMemoryBarrier.3.html#//apple_ref/doc/man/3/OSMemoryBarrier">OSMemoryBarrier 手册页</a>。</p>
<h3><a id="%E9%94%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>锁</h3>
<p>锁是最常用的同步工具之一。您可以使用锁来保护代码的关键部分，这是一段代码，一次只允许一个线程访问。例如，关键部分可能操纵特定数据结构或使用一次最多支持一个客户端的某些资源。通过在此部分周围放置一个锁，可以排除其他线程进行可能影响代码正确性的更改。</p>
<p>表 4-1 列出了程序员常用的一些锁。OS X 和 iOS 提供了大多数这些锁类型的实现，但不是全部。对于不受支持的锁类型，说明列说明了在平台上未实现这些锁的原因。</p>
<p>表 4-1：锁类型</p>
<table>
<thead>
<tr>
<th>锁</th>
<th style="text-align: center">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>互斥锁</td>
<td style="text-align: center">互斥锁作为资源周围的保护屏障。互斥锁是一种信号量，一次只允许访问一个线程。如果一个线程正在使用互斥锁并且另一个线程尝试获取它，则该线程会阻塞，直到互斥锁被其原始持有者释放。如果多个线程竞争相同的互斥锁，则一次只允许一个互斥锁访问它。</td>
</tr>
<tr>
<td>递归锁</td>
<td style="text-align: center">递归锁是互斥锁的变体。递归锁允许单个线程在释放之前多次获取锁。其他线程保持阻塞状态，直到锁的所有者释放锁的次数与获取锁的次数相同。在递归迭代期间主要使用递归锁，但也可以在多个方法分别需要获取锁的情况下使用递归锁。</td>
</tr>
<tr>
<td>读写锁</td>
<td style="text-align: center">读写锁也称为共享独占锁。这种类型的锁通常用于大规模操作，并且如果经常读取受保护的数据结构并且仅偶尔修改，则可以显着提高性能。在正常操作期间，多个读取器可以同时访问数据结构。但是，当线程想要写入结构时，它会阻塞，直到所有读取器释放锁，此时它获取锁并可以更新结构。当写入线程正在等待锁时，新的读取器线程会阻塞，直到写入线程完成。系统仅支持使用 POSIX 线程的读写锁。有关如何使用这些锁的更多信息，请参见 <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/pthread.3.html#//apple_ref/doc/man/3/pthread">pthread 手册页</a>。</td>
</tr>
<tr>
<td>分布式锁</td>
<td style="text-align: center">分布式锁提供进程级别的互斥访问。与真正的互斥锁不同，分布式锁定不会阻止进程或阻止进程运行。它只是报告锁何时繁忙，并让流程决定如何继续。</td>
</tr>
<tr>
<td>自旋锁</td>
<td style="text-align: center">自旋锁重复轮询其锁定条件，直到该条件成立为止。自旋锁最常用于多处理器系统，其中锁的预期等待时间很短。在这些情况下，轮询通常比阻塞线程更有效，这涉及上下文切换和线程数据结构的更新。由于它们的轮询性质，系统不提供自旋锁的任何实现，但您可以在特定情况下轻松实现它们。有关在内核中实现自旋锁的信息，请参阅 <a href="https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/KernelProgramming/About/About.html#//apple_ref/doc/uid/TP30000905">Kernel Programming Guide</a>。</td>
</tr>
<tr>
<td>双重检查锁</td>
<td style="text-align: center">双重检查锁是试图通过在锁定之前测试锁定标准来减少锁的开销。由于双重检查的锁可能不安全，因此系统不会为它们提供明确的支持，因此不鼓励使用它们。</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注意：大多数类型的锁还包含一个内存屏障，以确保在进入临界区之前完成任何先前的加载和存储指令。</p>
</blockquote>
<p>有关如何使用锁的信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW16">使用锁</a>。</p>
<h3><a id="%E6%9D%A1%E4%BB%B6" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>条件</h3>
<p>条件是另一种类型的信号量，它允许线程在某个条件为真时相互发信号。条件通常用于指示资源的可用性或确保以特定顺序执行任务。当一个线程测试一个条件时，它会阻塞，除非该条件已经为真。它会保持阻塞状态，直到某个其他线程明确更改并发出信号。条件和互斥锁之间的区别在于可以允许多个线程同时访问该条件。这个条件更像是一个守门员，它根据一些指定的标准让不同的线程通过门。</p>
<p>您可能使用条件的一种方法是管理待处理事件池。当队列中有事件时，事件队列将使用条件变量来发出等待线程的信号。如果一个事件到达，队列将适当地发出信号。如果一个线程已经在等待，它将被唤醒，然后它将从队列中拉出事件并处理它。如果两个事件大致同时进入队列，则队列将发出两次信号以唤醒两个线程。</p>
<p>该系统为几种不同技术的条件提供支持。但是，正确实现条件需要仔细编码，因此在使用条件之前，您应该查看<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW4">使用条件</a>中的示例。</p>
<h3><a id="%E6%89%A7%E8%A1%8C%E9%80%89%E6%8B%A9%E5%99%A8%E4%BE%8B%E7%A8%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>执行选择器例程</h3>
<p>Cocoa 应用程序具有以同步方式将消息传递到单个线程的便捷方式。<code>NSObject</code> 类声明了在应用程序的某个活动线程上执行选择器的方法。这些方法允许您的线程异步传递消息，并保证它们将由目标线程同步执行。例如，您可以使用执行选择器消息将分布式计算的结果传递到应用程序的主线程或指定的协调器线程。每个执行选择器的请求都在目标线程的运行循环中排队，然后按接收顺序依次处理请求。</p>
<p>有关执行选择器例程的摘要以及有关如何使用它们的更多信息，请参阅 <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW44">Cocoa 执行选择器源</a>。</p>
<h2><a id="%E5%90%8C%E6%AD%A5%E6%88%90%E6%9C%AC%E5%92%8C%E6%80%A7%E8%83%BD" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>同步成本和性能</h2>
<p><u>同步有助于确保代码的正确性，但这样做会牺牲性能。即使在无争议的情况下，使用同步工具也会引入延迟。</u>锁和原子操作通常涉及使用内存屏障和内核级同步来确保代码得到适当保护。如果存在锁争用，您的线程可能会阻塞并经历更大的延迟。</p>
<p>表 4-2 列出了与无争议情况下的互斥锁和原子操作相关的一些近似成本。这些测量值代表数千个样本的平均时间。与线程创建时间一样，互斥锁获取时间（即使在无争议的情况下）也会因处理器负载，计算机速度以及可用系统和程序存储器的数量而有很大差异。</p>
<p>表 4-2：互斥锁和原子操作成本</p>
<table>
<thead>
<tr>
<th>项目</th>
<th style="text-align: center">近似成本</th>
<th style="text-align: center">笔记</th>
</tr>
</thead>
<tbody>
<tr>
<td>互斥锁获取时间</td>
<td style="text-align: center">大约 0.2 微秒</td>
<td style="text-align: center">这是无争议情况下的锁定获取时间。如果锁由另一个线程保持，则获取时间可以更长。这些数据是通过分析在具有 2 GHz Core Duo 处理器和运行 OS X v10.5 的 1 GB RAM 的基于 Intel 的 iMac 上获取互斥锁时产生的平均值和中值来确定的。</td>
</tr>
<tr>
<td>原子比较和交换</td>
<td style="text-align: center">大约 0.05 微秒</td>
<td style="text-align: center">这是无争议情况下的比较和交换时间。这些数字是通过分析操作的平均值和中值来确定的，并且是在基于 Intel 的 iMac 上生成的，该 iMac 具有 2 GHz Core Duo 处理器和运行 OS X v10.5 的 1 GB RAM。</td>
</tr>
</tbody>
</table>
<p>在设计并发任务时，正确性始终是最重要的因素，但您也应该考虑性能因素。在多个线程下正确执行但比在单个线程上运行的相同代码慢的代码几乎不是改进。</p>
<p>如果要对现有的单线程应用程序进行改造，则应始终对关键任务的性能进行一组基线测量。添加其他线程后，您应该对这些相同的任务进行新的测量，并将多线程案例的性能与单线程案例进行比较。如果在调整代码后，线程不会提高性能，您可能需要重新考虑特定的实现或重新考虑完全使用线程。</p>
<p>有关性能和收集指标的工具的信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/Performance/Conceptual/PerformanceOverview/Introduction/Introduction.html#//apple_ref/doc/uid/TP40001410">性能概述</a>。有关锁和原子操作成本的特定信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/10000057i-CH15-SW7">线程成本</a>。</p>
<h2><a id="%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E5%92%8C%E4%BF%A1%E5%8F%B7" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>线程安全和信号</h2>
<p>当涉及到线程应用程序时，没有什么比处理信号的问题引起更多的恐惧或混乱。信号是一种低级 BSD 机制，可用于将信息传递给进程或以某种方式对其进行操作。有些程序使用信号来检测某些事件，例如子进程的死亡。系统使用信号终止失控过程并传达其他类型的信息。</p>
<p>信号问题不是它们的作用，而是当应用程序有多个线程时它们的行为。在单线程应用程序中，所有信号处理程序都在主线程上运行。在多线程应用程序中，与特定硬件错误（例如非法指令）无关的信号将传递给当时正在运行的任何线程。如果同时运行多个线程，则信号将传递给系统正在选择的任何一个线程。换句话说，信号可以传递到应用程序的任何线程。</p>
<p>在应用程序中实现信号处理程序的第一条规则是避免假设哪个线程正在处理信号。如果特定线程想要处理给定信号，则需要在信号到达时通过某种方式通知该线程。您不能只假设从该线程安装信号处理程序将导致信号传递到同一线程。</p>
<p>有关信号和安装信号处理程序的更多信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/signal.3.html#//apple_ref/doc/man/3/signal">信号</a>和 <a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sigaction.2.html#//apple_ref/doc/man/2/sigaction">sigaction手册页</a>。</p>
<h2><a id="%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E8%AE%BE%E8%AE%A1%E6%8F%90%E7%A4%BA" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>线程安全设计提示</h2>
<p>同步工具是使代码线程安全的有用方法，但它们不是灵丹妙药。使用过多，与其非线程性能相比，锁和其他类型的同步工具实际上可以降低应用程序的线程性能。在安全性和性能之间找到适当的平衡是一种需要经验的艺术。以下部分提供了一些提示，可帮助您为应用程序选择适当的同步级别。</p>
<h3><a id="%E5%AE%8C%E5%85%A8%E9%81%BF%E5%85%8D%E5%90%8C%E6%AD%A5" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>完全避免同步</h3>
<p>对于您正在处理的任何新项目，甚至对于现有项目，设计代码和数据结构以避免同步是最佳解决方案。虽然锁和其他同步工具很有用，但它们确实会影响任何应用程序的性能。如果整体设计导致特定资源之间存在高争用，那么您的线程可能会等待更长时间。</p>
<p><u>实现并发性的最佳方法是减少并发任务之间的交互和相互依赖性。</u>如果每个任务都在自己的私有数据集上运行，则不需要使用锁保护该数据。即使在两个任务共享公共数据集的情况下，您也可以查看设置分区的方法或为每个任务提供自己的副本。当然，复制数据集也有其成本，因此在做出决定之前，您必须权衡这些成本与同步成本。</p>
<h3><a id="%E4%BA%86%E8%A7%A3%E5%90%8C%E6%AD%A5%E7%9A%84%E9%99%90%E5%88%B6" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>了解同步的限制</h3>
<p>只有当应用程序中的所有线程一致地使用同步工具时，它们才有效。如果创建互斥锁以限制对特定资源的访问，则在尝试操作资源之前，所有线程都必须获取相同的互斥锁。如果不这样做会破坏互斥锁提供的保护，并且是程序员错误。</p>
<h3><a id="%E6%84%8F%E8%AF%86%E5%88%B0%E7%BC%96%E7%A0%81%E6%AD%A3%E7%A1%AE%E6%80%A7%E7%9A%84%E5%A8%81%E8%83%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>意识到编码正确性的威胁</h3>
<p>使用锁和内存屏障时，您应该始终仔细考虑它们在代码中的位置。即使看起来很好的锁也会让你陷入虚假的安全感。下面的一系列示例试图通过指出看似无害的代码中的缺陷来说明这个问题。基本前提是你有一个包含一组不可变对象的可变数组。假设您要调用数组中第一个对象的方法。您可以使用以下代码执行此操作：</p>
<pre><code class="language-objectivec">NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[arrayLock unlock];
[anObject doSomething];
</code></pre>
<p>因为数组是可变的，所以数组周围的锁会阻止其他线程修改数组，直到获得所需的对象。并且因为您检索的对象本身是不可变的，所以在调用 <code>doSomething</code> 方法时不需要锁定。</p>
<p>但是，前面的例子存在问题。如果您释放锁并且另一个线程进入并在您有机会执行 <code>doSomething</code> 方法之前从阵列中删除所有对象，会发生什么？ 在没有垃圾收集的应用程序中，可以释放代码所持有的对象，从而使 <code>anObject</code> 指向无效的内存地址。要解决此问题，您可能决定只需重新安排现有代码并在调用 <code>doSomething</code> 后释放锁定，如下所示：</p>
<pre><code class="language-objectivec">NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
 
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject doSomething];
[arrayLock unlock];
</code></pre>
<p>通过在锁内移动 <code>doSomething</code> 调用，您的代码可确保在调用方法时对象仍然有效。不幸的是，如果 <code>doSomething</code> 方法需要很长时间才能执行，这可能会导致代码长时间保持锁定，这可能会造成性能瓶颈。</p>
<p>代码的问题不是关键区域定义不明确，而是实际问题没有被理解。真正的问题是仅由其他线程的存在触发的内存管理问题。因为它可以被另一个线程释放，更好的解决方案是在释放锁之前保留 <code>anObject</code>。该解决方案解决了被释放对象的真正问题，并且没有引入潜在的性能损失。</p>
<pre><code class="language-objectivec">NSLock* arrayLock = GetArrayLock();
NSMutableArray* myArray = GetSharedArray();
id anObject;
 
[arrayLock lock];
anObject = [myArray objectAtIndex:0];
[anObject retain];
[arrayLock unlock];
 
[anObject doSomething];
[anObject release];
</code></pre>
<p>虽然前面的例子非常简单，但它们确实说明了非常重要的一点。说到正确性，你必须超越明显的问题。内存管理和设计的其他方面也可能受到多线程的影响，因此您必须事先考虑这些问题。此外，您应该始终假设编译器在安全性方面会做最糟糕的事情。这种意识和警惕应该可以帮助您避免潜在的问题，并确保您的代码正常运行。
有关如何使程序具有线程安全性的其他示例，请参阅<a href="ibooks:///#chapterguid(FD96B81C-605A-4CEC-84F9-B4B59C170A42)">线程安全摘要</a>。</p>
<h3><a id="%E6%B3%A8%E6%84%8F%E6%AD%BB%E9%94%81%E5%92%8C%E6%B4%BB%E9%94%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>注意死锁和活锁</h3>
<p>每当线程同时尝试获取多个锁时，就有可能发生死锁。当两个不同的线程持有另一个线程需要的锁，然后尝试获取另一个线程持有的锁时，就会发生死锁。结果是每个线程永久阻塞，因为它永远不会获得另一个锁。</p>
<p>活锁类似于死锁，当两个线程竞争同一组资源时发生。在活锁情况下，线程在尝试获取其第二个锁时放弃其第一个锁。一旦获得第二个锁，它就会返回并尝试再次获取第一个锁。它锁定是因为它花费所有时间释放一个锁并试图获得另一个锁，而不是做任何实际的工作。</p>
<p>避免死锁和活锁情况的最佳方法是一次只能使用一个锁。如果您必须一次获得多个锁，则应确保其他线程不会尝试执行类似的操作。</p>
<h3><a id="%E6%AD%A3%E7%A1%AE%E4%BD%BF%E7%94%A8%E6%98%93%E5%A4%B1%E6%80%A7%E5%8F%98%E9%87%8F%EF%BC%88volatile%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>正确使用易失性变量（Volatile）</h3>
<p>如果您已经使用互斥锁来保护代码段，请不要自动假设您需要使用 <code>volatile</code> 关键字来保护该部分中的重要变量。互斥锁包括一个内存屏障，以确保正确的加载和存储操作顺序。将 <code>volatile</code> 关键字添加到临界区中的变量会强制每次访问时从内存加载该值。在特定情况下，两种同步技术的组合可能是必要的，但也会导致显着的性能损失。如果仅使用互斥锁足以保护变量，则省略 <code>volatile</code> 关键字。</p>
<p>同样重要的是，不要使用 <code>volatile</code> 变量来避免使用互斥锁。通常，互斥锁和其他同步机制是比 <code>volatile</code> 变量更好的保护数据结构完整性的方法。<code>volatile</code> 关键字仅确保从内存加载变量而不是存储在寄存器中。它不能确保代码正确访问变量。</p>
<h2><a id="%E4%BD%BF%E7%94%A8%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用原子操作</h2>
<p>非阻塞同步是一种执行某些类型操作并避免锁消耗的方法。尽管锁是同步两个线程的有效方法，但获取锁是一种相对昂贵的操作，即使在无争议的情况下也是如此。相比之下，许多原子操作只需要一小部分时间就可以完成，并且可以像锁一样有效。</p>
<p><u>通过原子操作，您可以对 32 位或 64 位值执行简单的数学和逻辑运算。这些操作依赖于特殊的硬件指令（和可选的内存屏障），以确保在再次访问受影响的内存之前完成给定的操作。在多线程情况下，您应始终使用包含内存屏障的原子操作，以确保在线程之间正确同步内存。</u></p>
<p>表 4-3 列出了可用的原子数学和逻辑运算以及相应的函数名称。这些函数都在 /usr/include/libkern/OSAtomic.h 头文件中声明，您也可以在其中找到完整的语法。这些功能的 64 位版本仅在 64 位进程中可用。</p>
<p>表 4-3：原子数学和逻辑运算</p>
<table>
<thead>
<tr>
<th>操作</th>
<th>函数名</th>
<th style="text-align: center">描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>相加</td>
<td><code>OSAtomicAdd64 OSAtomicAdd64Barrier</code> <code>OSAtomicAdd64 OSAtomicAdd64Barrier</code></td>
<td style="text-align: center">将两个整数值相加，并将结果存储在其中一个指定变量中。</td>
</tr>
<tr>
<td>递增</td>
<td><code>OSAtomicIncrement32</code> <code>OSAtomicIncrement32Barrier</code> <code>OSAtomicIncrement64</code> <code>OSAtomicIncrement64Barrier</code></td>
<td style="text-align: center">将指定的整数值递增 1。</td>
</tr>
<tr>
<td>递减</td>
<td><code>OSAtomicDecrement32</code> <code>OSAtomicDecrement32Barrier</code> <code>OSAtomicDecrement64</code> <code>OSAtomicDecrement64Barrier</code></td>
<td style="text-align: center">将指定的整数值减 1。</td>
</tr>
<tr>
<td>逻辑或</td>
<td><code>OSAtomicOr32</code> <code>OSAtomicOr32Barrier</code></td>
<td style="text-align: center">在指定的 32 位值和 32 位掩码之间执行逻辑 OR。</td>
</tr>
<tr>
<td>逻辑与</td>
<td><code>OSAtomicAnd32</code> <code>OSAtomicAnd32Barrier</code></td>
<td style="text-align: center">在指定的 32 位值和 32 位掩码之间执行逻辑 AND。</td>
</tr>
<tr>
<td>逻辑异或</td>
<td><code>OSAtomicXor32</code> <code>OSAtomicXor32Barrier</code></td>
<td style="text-align: center">在指定的 32 位值和 32 位掩码之间执行逻辑 XOR。</td>
</tr>
<tr>
<td>比较和交换</td>
<td><code>OSAtomicCompareAndSwap32</code> <code>OSAtomicCompareAndSwap32Barrier</code> <code>OSAtomicCompareAndSwap64</code> <code>OSAtomicCompareAndSwap64Barrier</code> <code>OSAtomicCompareAndSwapPtr</code> <code>OSAtomicCompareAndSwapPtrBarrier</code> <code>OSAtomicCompareAndSwapInt</code> <code>OSAtomicCompareAndSwapIntBarrier</code> <code>OSAtomicCompareAndSwapLong</code> <code>OSAtomicCompareAndSwapLongBarrier</code></td>
<td style="text-align: center">将变量与指定的旧值进行比较。如果两个值相等，则此函数将指定的新值分配给变量；否则，它什么都不做。比较和赋值作为一个原子操作完成，函数返回一个布尔值，指示交换是否实际发生。</td>
</tr>
<tr>
<td>测试和设置</td>
<td><code>OSAtomicTestAndSet</code> <code>OSAtomicTestAndSetBarrier</code></td>
<td style="text-align: center">在指定变量中测试一下，将该位设置为 1，并将旧位的值作为布尔值返回。根据字节（（char *）地址+（n &gt;&gt; 3））的公式（0x80 &gt;&gt;（n＆7））测试位，其中 n 是位数，地址是指向变量的指针。该公式有效地将变量分解为 8 位大小的块，并反向排序每个块中的位。例如，要测试 32 位整数的最低位（位0），实际上会为位号指定 7；同样，要测试最高位（位32），您可以为位号指定 24。</td>
</tr>
<tr>
<td>测试和清除</td>
<td><code>OSAtomicTestAndClear</code> <code>OSAtomicTestAndClearBarrier</code></td>
<td style="text-align: center">在指定变量中测试一下，将该位设置为 0，并将旧位的值作为布尔值返回。根据字节（（char *）地址+（n &gt;&gt; 3））的公式（0x80 &gt;&gt;（n＆7））测试位，其中 n 是位数，地址是指向变量的指针。该公式有效地将变量分解为 8 位大小的块，并反向排序每个块中的位。例如，要测试32位整数的最低位（位0），实际上会为位号指定 7；同样，要测试最高位（位32），您可以为位号指定 24。</td>
</tr>
</tbody>
</table>
<p>大多数原子函数的行为应该是相对简单的，如你期望的。但是，代码 4-1 显示了原子测试和设置以及比较和交换操作的行为，这些操作稍微复杂一些。对 <code>OSAtomicTestAndSet</code> 函数的前三次调用演示了如何在整数值上使用位操作公式，其结果可能与您期望的不同。最后两个调用显示 <code>OSAtomicCompareAndSwap32</code> 函数的行为。在所有情况下，当没有其他线程正在操作值时，在无争议的情况下调用这些函数。</p>
<p>有关原子操作的信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/atomic.3.html#//apple_ref/doc/man/3/atomic">原子手册页</a>和 /usr/include/libkern/OSAtomic.h 头文件。</p>
<p>代码 4-1：执行原子操作</p>
<pre><code class="language-objectivec">int32_t  theValue = 0;
OSAtomicTestAndSet(0, &amp;theValue);
// theValue is now 128.
 
theValue = 0;
OSAtomicTestAndSet(7, &amp;theValue);
// theValue is now 1.
 
theValue = 0;
OSAtomicTestAndSet(15, &amp;theValue)
// theValue is now 256.
 
OSAtomicCompareAndSwap32(256, 512, &amp;theValue);
// theValue is now 512.
 
OSAtomicCompareAndSwap32(256, 1024, &amp;theValue);
// theValue is still 512.
</code></pre>
<p>有关原子操作的信息，请参阅<a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/atomic.3.html#//apple_ref/doc/man/3/atomic">原子手册页</a>和 /usr/include/libkern/OSAtomic.h 头文件。</p>
<h2><a id="%E4%BD%BF%E7%94%A8%E9%94%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用锁</h2>
<p>锁是线程编程的基本同步工具。锁可以让您轻松保护大部分代码，以确保代码的正确性。OS X 和 iOS 为所有应用程序类型提供基本的互斥锁，而 Foundation 框架为特殊情况定义了互斥锁的一些其他变体。以下部分介绍如何使用其中几种锁类型。</p>
<h3><a id="%E4%BD%BF%E7%94%A8posix%E4%BA%92%E6%96%A5%E9%94%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 POSIX 互斥锁</h3>
<p>POSIX 互斥锁非常易于在任何应用程序中使用。要创建互斥锁，请声明并初始化 <code>pthread_mutex_t</code> 结构。要锁定和解锁互斥锁，请使用 <code>pthread_mutex_lock</code> 和 <code>pthread_mutex_unlock</code> 函数。代码 4-2 显示了初始化和使用 POSIX 线程互斥锁所需的基本代码。完成锁定后，只需调用 <code>pthread_mutex_destroy</code> 即可释放锁数据结构。</p>
<p>代码 4-2：使用互斥锁</p>
<pre><code class="language-c">pthread_mutex_t mutex;
void MyInitFunction()
{
    pthread_mutex_init(&amp;mutex, NULL);
}
 
void MyLockingFunction()
{
    pthread_mutex_lock(&amp;mutex);
    // Do work.
    pthread_mutex_unlock(&amp;mutex);
}
</code></pre>
<blockquote>
<p>注意：前面的代码是一个简化的示例，旨在显示 POSIX 线程互斥锁函数的基本用法。您自己的代码应该检查这些函数返回的错误代码并适当地处理它们。</p>
</blockquote>
<h3><a id="%E4%BD%BF%E7%94%A8nslock%E7%B1%BB" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 NSLock 类</h3>
<p><code>NSLock</code> 对象为 Cocoa 应用程序实现了一个基本互斥锁。所有锁（包括 <code>NSLock</code>）的接口实际上由 <code>NSLocking</code> 协议定义，该协议定义了锁定和解锁方法。您可以像使用任何互斥锁一样使用这些方法获取和释放锁。</p>
<p>除了标准锁定行为之外，NSLock 类还添加了 <code>tryLock</code> 和 <code>lockBeforeDate:</code> 方法。<code>tryLock</code> 方法尝试获取锁，但是如果锁不可用则不会阻止；相反，该方法只返回 <code>NO</code>。<code>lockBeforeDate:</code> 方法尝试获取锁但是如果在指定的时间限制内未获取锁定，则取消阻塞线程（并返回 <code>NO</code>）。</p>
<p>下面的示例演示如何使用 <code>NSLock</code> 对象协调可视显示的更新，可视显示的数据由多个线程计算。如果线程无法立即获取锁，它只会继续计算，直到它可以获取锁并更新显示。</p>
<pre><code class="language-objectivec">BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
...
while (moreToDo) {
    /* Do another increment of calculation */
    /* until there’s no more to do. */
    if ([theLock tryLock]) {
        /* Update display used by all threads. */
        [theLock unlock];
    }
}
</code></pre>
<h3><a id="%E4%BD%BF%E7%94%A8synchronized%E6%8C%87%E4%BB%A4" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 @synchronized 指令</h3>
<p><code>@synchronized</code> 指令是一种在 objectivec 代码中动态创建互斥锁的便捷方法。<code>@synchronized</code> 指令执行任何其他互斥锁将执行的操作 - 它可以防止不同的线程同时获取相同的锁。但是，在这种情况下，您不必直接创建互斥锁或锁定对象。相反，您只需使用任何 objectivec 对象作为锁定标识符，如以下示例所示：</p>
<pre><code class="language-objectivec">- (void)myMethod:(id)anObj
{
    @synchronized(anObj)
    {
        // Everything between the braces is protected by the @synchronized directive.
    }
}
</code></pre>
<p>传递给 <code>@synchronized</code> 指令的对象是用于区分受保护块的唯一标识符。如果在两个不同的线程中执行上述方法，则在每个线程上为 <code>anObj</code> 参数传递一个不同的对象，每个线程都将锁定并继续处理而不被另一个阻塞。但是，如果在两种情况下都传递相同的对象，则其中一个线程将首先获取锁定，另一个线程将阻塞，直到第一个线程完成关键部分。</p>
<p>作为预防措施，<code>@synchronized</code> 块隐式地向受保护代码添加异常处理程序。如果抛出异常，此处理程序会自动释放互斥锁。这意味着，为了使用 <code>@synchronized</code> 指令，还必须在代码中启用 objectivec 异常处理。如果您不希望由隐式异常处理程序引起额外开销，则应考虑使用锁类。</p>
<p>有关 <code>@synchronized</code> 指令的更多信息，请参阅 <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011210">Objective-C 编程语言</a>。</p>
<h3><a id="%E4%BD%BF%E7%94%A8%E5%85%B6%E4%BB%96cocoa%E9%94%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用其他 Cocoa 锁</h3>
<p>以下部分描述了使用其他几种 Cocoa 锁的过程。</p>
<h4><a id="%E4%BD%BF%E7%94%A8nsrecursivelock%E5%AF%B9%E8%B1%A1" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 NSRecursiveLock 对象</h4>
<p><code>NSRecursiveLock</code> 类定义了一个锁，<u>该锁可以由同一个线程多次获取，而不会导致线程死锁。</u>递归锁会跟踪成功获取的次数。每次成功获取锁必须通过相应的调用来解锁锁。只有当所有锁定和解锁调用都是平衡的时候，锁才会实际释放，以便其他线程可以获取它。</p>
<p>顾名思义，这种类型的锁通常在递归函数中使用，以防止递归阻塞线程。您可以类似地在非递归情况下使用它来调用语义要求它们也接受锁定的函数。这是一个简单的递归函数的例子，它通过递归获取锁。如果您没有为此代码使用 <code>NSRecursiveLock</code> 对象，则在再次调用该函数时该线程将会死锁。</p>
<pre><code class="language-objectivec">NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
 
void MyRecursiveFunction(int value)
{
    [theLock lock];
    if (value != 0)
    {
        --value;
        MyRecursiveFunction(value);
    }
    [theLock unlock];
}
 
MyRecursiveFunction(5);
</code></pre>
<blockquote>
<p>注意：由于在所有锁定调用与解锁调用平衡之前不会释放递归锁定，因此您应仔细权衡使用性能锁定对潜在性能影响的决策。长时间保持锁定会导致其他线程阻塞，直到递归完成。如果您可以重写代码以消除递归或消除使用递归锁的需要，则可以获得更好的性能。</p>
</blockquote>
<h4><a id="%E4%BD%BF%E7%94%A8nsconditionlock%E5%AF%B9%E8%B1%A1" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 NSConditionLock 对象</h4>
<p><code>NSConditionLock</code> 对象定义一个可以使用特定值锁定和解锁的互斥锁。<u>您不应该将此类型的锁与条件混淆</u>（请参阅<a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-126424">条件</a>）。这种行为有点类似于条件，但实现方式却截然不同。</p>
<p>通常，当线程需要按特定顺序执行任务时，例如当一个线程生成另一个线程消耗的数据时，您使用 <code>NSConditionLock</code> 对象。当生产者正在执行时，使用者使用特定于您的程序的条件获取锁。（条件本身只是您定义的整数值。）当生产者完成时，它解锁锁定并将锁定条件设置为适当的整数值以唤醒使用者线程，然后继续处理数据。</p>
<p><code>NSConditionLock</code> 对象响应的锁定和解锁方法可以任意组合使用。例如，您可以将 <code>lock</code> 消息与 <code>unlockWithCondition:</code> 配对，或者将 <code>lockWhenCondition:</code> 消息与 <code>unlock</code> 配对。当然，后一种组合解锁了锁，但可能不释放任何等待特定条件值的线程。</p>
<p>以下示例显示如何使用条件锁处理生产者 - 使用者问题。想象一下，应用程序包含一个数据队列。生产者线程将数据添加到队列，而使用者线程从队列中提取数据。生产者不需要等待特定条件，但它必须等待锁可用，以便它可以安全地将数据添加到队列中。</p>
<pre><code class="language-objectivec">id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
 
while(true)
{
    [condLock lock];
    /* Add data to the queue. */
    [condLock unlockWithCondition:HAS_DATA];
}
</code></pre>
<p>因为锁的初始条件设置为 <code>NO_DATA</code>，所以生产者线程最初获取锁应该没有问题。它用数据填充队列并将条件设置为 <code>HAS_DATA</code>。在后续迭代期间，生产者线程可以在到达时添加新数据，无论队列是空的还是仍有一些数据。它阻塞的唯一时间是使用者线程从队列中提取数据。</p>
<p>由于使用者线程必须具有要处理的数据，因此它使用特定条件在队列上等待。当生产者将数据放入队列时，消费者线程会唤醒并获取其锁。然后，它可以从队列中提取一些数据并更新队列状态。以下示例显示了使用者线程的处理循环的基本结构。</p>
<pre><code class="language-objectivec">while (true)
{
    [condLock lockWhenCondition:HAS_DATA];
    /* Remove data from the queue. */
    [condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)];
 
    // Process the data locally.
}
</code></pre>
<h4><a id="%E4%BD%BF%E7%94%A8nsdistributedlock%E5%AF%B9%E8%B1%A1" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 NSDistributedLock 对象</h4>
<p>多个主机上的多个应用程序可以使用 <code>NSDistributedLock</code> 类来限制对某些共享资源（如文件）的访问。锁本身实际上是一个互斥锁，它使用文件系统项（例如文件或目录）实现。要使 <code>NSDistributedLock</code> 对象可用，锁必须可由所有使用它的应用程序写入。这通常意味着将其放在文件系统上，该文件系统可供运行该应用程序的所有计算机访问。</p>
<p>与其他类型的锁不同，<code>NSDistributedLock</code> 不符合 <code>NSLocking</code> 协议，因此没有 <code>lock</code> 方法。<code>lock</code> 方法将阻止线程的执行并要求系统以预定速率轮询锁定。<code>NSDistributedLock</code> 不是对您的代码强加此机制，而是提供 <code>tryLock</code> 方法，让您决定是否进行轮询。</p>
<p>因为它是使用文件系统实现的，所以除非所有者明确释放 <code>NSDistributedLock</code> 对象，否则不会释放它。如果您的应用程序在持有分布式锁时崩溃，则其他客户端将无法访问受保护资源。在这种情况下，您可使用 <code>breakLock</code> 方法来破坏现有锁，以便您可以获取它。但是，通常应该避免断开锁定，除非您确定拥有过程已经死亡并且无法释放锁定。</p>
<p>与其他类型的锁一样，当您使用 <code>NSDistributedLock</code> 对象时，可以通过调用 <code>unlock</code> 方法释放它。</p>
<h2><a id="%E4%BD%BF%E7%94%A8%E6%9D%A1%E4%BB%B6" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用条件</h2>
<p>条件是一种特殊类型的锁，可用于同步操作必须进行的顺序。它们以微妙的方式与互斥锁不同。等待条件的线程将保持阻塞状态，直到该条件由另一个线程显式发出信号。</p>
<p>由于实现操作系统所涉及的微妙之处，即使条件锁实际上没有通过代码发出信号，也允许条件锁返回虚假成功。为避免这些虚假信号引起的问题，您应始终将谓词与条件锁一起使用。谓词是一种更具体的方法，用于确定线程是否安全。该条件只是让您的线程保持睡眠状态，直到可以通过信令线程设置谓词。</p>
<p>以下部分介绍如何在代码中使用条件。</p>
<h3><a id="%E4%BD%BF%E7%94%A8nscondition%E7%B1%BB" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 NSCondition 类</h3>
<p><code>NSCondition</code> 类提供与 POSIX 条件相同的语义，但将所需的锁和条件数据结构包装在单个对象中。结果是一个对象，您可以像互斥锁一样锁定，然后像条件一样等待。</p>
<p>代码 4-3 显示了一个代码片段，演示了等待 <code>NSCondition</code> 对象的事件序列。<code>cocoaCondition</code> 变量包含一个 <code>NSCondition</code> 对象，<code>timeToDoWork</code> 变量是一个整数，它在发出条件信号之前立即从另一个线程递增。</p>
<p>代码 4-3：使用 Cocoa 条件</p>
<pre><code class="language-objectivec">[cocoaCondition lock];
while (timeToDoWork &lt;= 0)
    [cocoaCondition wait];
 
timeToDoWork--;
 
// Do real work here.
 
[cocoaCondition unlock];
</code></pre>
<p>代码 4-4 显示了用于表示 Cocoa 条件并增加谓词变量的代码。在发出信号之前，您应该始终锁定条件。</p>
<p>代码 4-4：发出 Cocoa 条件信号</p>
<pre><code class="language-objectivec">[cocoaCondition lock];
timeToDoWork++;
[cocoaCondition signal];
[cocoaCondition unlock];
</code></pre>
<h3><a id="%E4%BD%BF%E7%94%A8posix%E6%9D%A1%E4%BB%B6" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>使用 POSIX 条件</h3>
<p>POSIX 线程条件锁需要使用条件数据结构和互斥锁。尽管两个锁结构是分开的，但互斥锁在运行时与条件结构紧密相关。等待信号的线程应始终使用相同的互斥锁和条件结构。更改配对可能会导致错误。</p>
<p>代码 4-5 显示了条件和谓词的基本初始化和使用。在初始化条件和互斥锁之后，等待线程使用 <code>ready_to_go</code> 变量作为其谓词进入 <code>while</code> 循环。只有在设置谓词并且随后发出条件信号时，等待线程才会唤醒并开始执行其工作。</p>
<p>代码 4-5：使用 POSIX 条件</p>
<pre><code class="language-c++">pthread_mutex_t mutex;
pthread_cond_t condition;
Boolean     ready_to_go = true;
 
void MyCondInitFunction()
{
    pthread_mutex_init(&amp;mutex);
    pthread_cond_init(&amp;condition, NULL);
}
void MyWaitOnConditionFunction()
{
    // Lock the mutex.
    pthread_mutex_lock(&amp;mutex);
 
    // If the predicate is already set, then the while loop is bypassed;
    // otherwise, the thread sleeps until the predicate is set.
    while(ready_to_go == false)
    {
        pthread_cond_wait(&amp;condition, &amp;mutex);
    }
    // Do work. (The mutex should stay locked.)
 
    // Reset the predicate and release the mutex.
    ready_to_go = false;
    pthread_mutex_unlock(&amp;mutex);
}
</code></pre>
<p>信令线程负责设置谓词和将信号发送到条件锁。代码 4-6 显示了实现此行为的代码。在此示例中，条件在互斥锁内部发出信号，以防止在等待条件的线程之间发生竞争条件。</p>
<p>代码 4-6：向条件锁发信号</p>
<pre><code class="language-c">void SignalThreadUsingCondition()
{
    // At this point, there should be work for the other thread to do.
    pthread_mutex_lock(&amp;mutex);
    ready_to_go = true;
 
    // Signal the other thread to begin work.
    pthread_cond_signal(&amp;condition);
 
    pthread_mutex_unlock(&amp;mutex);
}
</code></pre>
<blockquote>
<p>注意：前面的代码是一个简化的示例，旨在显示 POSIX 线程条件函数的基本用法。您自己的代码应该检查这些函数返回的错误代码并适当地处理它们。</p>
</blockquote>
<div style="padding-top:25px;">
    <div style="float:left;">
        <a style="text-decoration:none" href="%E8%BF%90%E8%A1%8C%E5%BE%AA%E7%8E%AF.html">&laquo; 上一章：运行循环（Run Loops）</a>
    </div>
    <div style="float:right">
        <a style="text-decoration:none" href="%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E6%91%98%E8%A6%81.html">下一章：附录 A &raquo;</a> 
    </div>
</div>

                  </article>
                  <div class="comments-wrap">
                    <div class="share-comments">
                      

                      

                      
                    </div>
                  </div><!-- end comments wrap -->
              </div>
            </div><!-- end columns -->
      </div><!-- end container -->
    </section>



    <footer class="footer">
        <div class="content has-text-centered">
          <p>
              Copyright &copy; 2019
              Powered by <a target="_blank" href="http://www.mweb.im">MWeb</a>,&nbsp; 
              Theme used <a target="_blank" href="https://bulma.io/">Bulma CSS</a>.
          </p>
        </div>
      </footer>



  













<script src="asset/prism.js"></script>



  
    




  </body>
</html>
